/*******************************************************************************
 * Copyright (c) 2011 TXT e-solutions SpA
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * This work was performed within the IoT_at_Work Project
 * and partially funded by the European Commission's
 * 7th Framework Programme under the research area ICT-2009.1.3
 * Internet of Things and enterprise environments.
 *
 *
 * Authors:
 *     Cristoforo Seccia (TXT e-solutions SpA)
 *
 * Contributors:
 *      Domenico Rotondi (TXT e-solutions SpA)
 *******************************************************************************/
package it.txt.access.capability.commons.schema.factory;

/**
 *
 * @author Cristoforo Seccia (TXT e-solutions SpA)
 */
import it.txt.access.capability.commons.osgi.CapabilityCommonsActivator;
import it.txt.access.capability.commons.schema.CapabilityNamespacesResolver;
import it.txt.access.capability.commons.schema.validation.CapabilitySchemaValidationHandler;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.bind.Marshaller;

import org.apache.xerces.jaxp.validation.XMLSchemaFactory;
import org.xml.sax.SAXException;

import com.sun.xml.bind.marshaller.NamespacePrefixMapper;

public class CapabilitySchemasJaxbFaxtory {

    public static final String FACTORY_CLASS = "org.apache.xerces.jaxp.validation.XMLSchemaFactory";//"com.sun.org.apache.xerces.internal.jaxp.validation.XMLSchemaFactory";
    public static final String FACTORY_PROPERTY = SchemaFactory.class.getName();
    private final CapabilitySchemaValidationHandler capabilitySchemaHandler = new CapabilitySchemaValidationHandler();
    private static final Logger LOGGER = Logger.getLogger(CapabilitySchemasJaxbFaxtory.class.getName());
    public static final String MAPPER_CLASS = "com.sun.xml.bind.namespacePrefixMapper";
    private JAXBContext context;
    private Unmarshaller unmarshaller;
    private Marshaller marshaller;
    private final Schema schema;
    private final NamespacePrefixMapper namespaceMapper;

    public CapabilitySchemasJaxbFaxtory(final Set<CapabilitySchemaSource> schemaSources,
            NamespacePrefixMapper namespaceMapper, Class<?>... classes) throws SAXException, JAXBException {
        this.namespaceMapper = namespaceMapper;
        String schemaFactoryProperty = FACTORY_PROPERTY
                + ":" + XMLConstants.W3C_XML_SCHEMA_NS_URI;

        if (LOGGER.isLoggable(Level.FINER))
            LOGGER.log(Level.FINER, "Default property value = " + System.getProperty(schemaFactoryProperty));
        
        System.setProperty(schemaFactoryProperty, FACTORY_CLASS);
        
        if (LOGGER.isLoggable(Level.FINER)) {
            LOGGER.log(Level.FINER, "NEW property value = " + System.getProperty(schemaFactoryProperty));
        }

        //TODO DELETE
//        try {
//            Class.forName(FACTORY_CLASS);
//        } catch (ClassNotFoundException e1) {
//            // TODO Auto-generated catch block
//            e1.printStackTrace();
//        }
        SchemaFactory schemaFactory =
                new XMLSchemaFactory();
//                SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
        schemaFactory.setResourceResolver(
                new CapabilityNamespacesResolver(schemaSources));
        
        schema = schemaFactory.newSchema(schemaSources.toArray(
                new CapabilitySchemaSource[schemaSources.size()]));
        try {
            if (LOGGER.isLoggable(Level.FINER)) {
                LOGGER.finer("JAXB context:: default initialization...");
            }
            context = JAXBContext.newInstance(classes);
        } catch (Throwable e) {
            if (LOGGER.isLoggable(Level.FINER)) {
                LOGGER.log(Level.FINER, "An error occurred while initialising the JAXB context", e);
            }
            ClassLoader defaultClassLoader = Thread.currentThread().getContextClassLoader();
            if (LOGGER.isLoggable(Level.FINER))
                LOGGER.finer("Default initialization of JAXB context failed. Bundle initialization...");            
            
            if (CapabilityCommonsActivator.getContextClassLoader() != null) {
                if (LOGGER.isLoggable(Level.FINER))
                    LOGGER.finer("Using CapabilityCommonsActivator Class loader...");
                Thread.currentThread().setContextClassLoader(CapabilityCommonsActivator.getContextClassLoader());
            } else {
                if (LOGGER.isLoggable(Level.FINER))
                    LOGGER.finer("Using CapabilitySchemasJaxbFaxtory Class loader...");
                Thread.currentThread().setContextClassLoader(CapabilitySchemasJaxbFaxtory.class.getClassLoader());
            }
            context = JAXBContext.newInstance(classes);
            //reset the class loader
            Thread.currentThread().setContextClassLoader(defaultClassLoader);
        }
        LOGGER.finer("JAXB Context initialized correctly.");
    }

    public CapabilitySchemaValidationHandler getCapabilitySchemaValidationHandler() {
        return capabilitySchemaHandler;
    }

    public Unmarshaller getUnmarshaller() {
        if (unmarshaller == null) {
            newUnmarshall();
        }
        return unmarshaller;
    }

    private void newUnmarshall() {
        try {
            unmarshaller = context.createUnmarshaller();
            unmarshaller.setSchema(schema);
            unmarshaller.setEventHandler(capabilitySchemaHandler);
        } catch (JAXBException ex) {
            LOGGER.log(Level.SEVERE, ex.getMessage(), ex);
            throw new RuntimeException("An error occured while creating a JAXB unmarshaller." + ex);
        }
    }

    public Marshaller getMarshaller() {
        if (marshaller == null) {
            newMarshaller();
        }
        return marshaller;
    }

    private void newMarshaller() {
        try {
            marshaller = context.createMarshaller();
            marshaller.setSchema(schema);
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
            marshaller.setEventHandler(capabilitySchemaHandler);
            marshaller.setProperty(MAPPER_CLASS, namespaceMapper);
        } catch (JAXBException ex) {
            LOGGER.log(Level.SEVERE, ex.getMessage(), ex);
            throw new RuntimeException("An error occured while creating a JAXB marshaller.", ex);
        }
    }
}
